package br.ufpe.cin.mac.middleware.connectors.concrete;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import br.ufpe.cin.mac.middleware.connectors.ConcreteConnector;
import br.ufpe.cin.mac.util.Constants;
import br.ufpe.cin.mac.util.Marshal;
import br.ufpe.cin.mac.util.Message;

/**
 * Class TcpClient
 * 
 * @author Osmany
 * @author Rafael Roque
 * 
 */
public class UDPClient extends ConcreteConnector implements UDPClientMBean {

	public static String server;
	public static int port;
	private Map<String, DatagramChannel> connections;
	public static UDPClient reference;

	public UDPClient() {		
		super(UDPClient.class.getCanonicalName());
		this.connections = Collections
				.synchronizedMap(new HashMap<String, DatagramChannel>());
		this.server = "127.0.0.1";
		this.port = Constants.UDP_PORT;
		this.reference = this;
	}

	public void disconnect(String server) throws IOException {
		DatagramChannel connection = this.connections.remove(server);
		connection.close();
	}

	public DatagramChannel connect(String receiverOBJ, String host, int port)
			throws Exception {
		DatagramChannel channel = DatagramChannel.open();
		InetSocketAddress addr = new InetSocketAddress(host,
				port);
		channel.connect(addr);

		connections.put(receiverOBJ, channel);
		return channel;
	}

	public void invC(Message msg) {

		msg.readOperations();

		this.invS.put(msg);

		sendNotification(msg.hashCode(), "invS");
	}

	public Message invS(int hashMsg) {
		try {
			final Message msg = (Message) this.invS.get(hashMsg);

			this.send(msg);
			new Thread() {
				public void run() {
					receive(msg.getReceiver());
				}
			}.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public Message terC(int hashMsg) {
		return (Message) this.terC.get(hashMsg);
	}

	public void terS(Message msg) {
		msg.readOperations();

		this.terC.put(msg);

		sendNotification(msg.hashCode(), "terC");
	}

	private void send(Message msg) {
		try {

			String receiverOBJ = msg.getReceiver();
			DatagramChannel channel;

		

			if (!isConnected(receiverOBJ)) {
		
				channel = this.connect(receiverOBJ, server, port);
			} else {
				channel = this.connections.get(receiverOBJ);
			}

			synchronized (channel) {
				byte[] send = Marshal.serializeObject(msg);
				int size = send.length;
				ByteBuffer buffer = ByteBuffer.allocate(4);
				byte[] msgSize = new byte[4];
				for (int i = 0; i < 4; i++) {
					msgSize[i] = (new Byte(
							(byte) (0x000000FF & (size >> (24 - i * 8)))));
				}

				buffer.put(msgSize);
				buffer.flip();
				int numSend = channel.write(buffer);
				if (numSend != 4) {
					System.err.println("ERROR: Send buffer UDP");
					return;
				}

				buffer = ByteBuffer.allocate(size);
				buffer.clear();
				buffer.put(send);
				buffer.flip();
				numSend = channel.write(buffer);
				if (numSend != send.length) {
					System.err.println("ERROR: Send buffer UDP");
					return;
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/*
	 * Nao recebe, trava pelo synchronized
	 */
	private void receive(String objName) {

		try {
			ByteBuffer buffer = ByteBuffer.allocate(4);
			buffer.clear();
			DatagramChannel channel = this.connections.get(objName);
			synchronized (channel) {
				channel.receive(buffer);
				byte[] readSyze = buffer.array();
				int msgSize = ((0x00000000000000FF & ((Byte) readSyze[0]) << 24)
						| ((0x00000000000000FF & ((Byte) readSyze[1])
								.byteValue()) << 16)
						| ((0x00000000000000FF & ((Byte) readSyze[2])
								.byteValue()) << 8) | (0x00000000000000FF & ((Byte) readSyze[3])
						.byteValue()));
				buffer = null;
				readSyze = null;

				buffer = ByteBuffer.allocate(msgSize);
				buffer.clear();
				channel.read(buffer);
			}

			Message msg = (Message) Marshal.recoverObject(buffer.array());
			this.terS(msg);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	private boolean isConnected(String objName) {
		if (this.connections.containsKey(objName)) {


			DatagramChannel channel = this.connections.get(objName);	
			return channel.isConnected();
		} else
			return false;
	}


	protected void msgManager(Message msg) {
	}


	public void setHost(String host) {
		String[] split = host.split(":");
		server = split[0];
		port = Integer.parseInt(split[1]);
		
	}
}